//
// Created by suhuamo on 25-7-16.
//
#include<iostream>
#include<WS2tcpip.h>
using namespace std;

/**
 *
 * ffmpeg 命令行生成 m3u8 切片:
 * ffmpeg -i input.mp4 -c:v libx264 -c:a copy -f hls -hls_time 3 -hls_list_size 0 hls/index.m3u8
 * 	备注：-hls_time n: 设置每片的长度，默认值为2,单位为秒
	     -hls_list_size n:设置播放列表保存的最多条目，设置为0会保存有所片信息，默认值为5
	     -hls_wrap n:设置多少片之后开始覆盖，如果设置为0则不会覆盖，默认值为0, 这个选项能够避免在磁盘上存储过多的片，而且能够限制写入磁盘的最多的片的数量

 * 测试:
 * 1. 使用ffplay测试，命令如下：
 *      ffplay http://127.0.0.1:8080/index.m3u8
 * 2. 使用 hlsPlay.html 测试
 *
 *  hls讲解：
 *  实际上就是把原始文件直接通过http发送出去，不像rtmp和rtsp一样有很多分包拆包等逻辑处理，原理很简单。
 *  但是如果直接发.mp4文件的话太大了，http报文也撑不住，故做了一次优化，即把.mp4文件拆分成了许多个小文件，小文件的名称后缀改为了.ts
 *  然后又会创建一个 .m3u8 的文件来保存.mp4一共拆分了多少个小文件，这些小文件的名称是什么，每一个小文件的时长是多少
 *  这样就可以实现跳转功能了，即拿到了 .m3u8 文件后，想跳转到视频一分钟的位置，那么计算一下一分钟的时候应该播放哪个小文件，然后通过http请求把对应的小文件下载下来即可。
 *  故流程如下：
 *  1. 第一次http请求，服务器返回 .m3u8 文件
 *  2. 接下来的请求，客户端会指定要下载哪个 .ts 小文件，服务器返回对应的小文件即可
 *  ....
 *  3. 直到所有 .ts 小文件下载完毕流程结束
 *  需要注意的不同点可能就是请求头中的 Content-Type 参数了。
 */

// 如果读取ts文件过大，这里的大小也要对应修改
char buf[1024*1024*5];

void doClient(int clientFd);

int main() {
    int port = 8080;
    printf("hlsServer http://127.0.0.1:%d/index.m3u8\n", port);

    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup error");
        return -1;
    }

    int serverFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    SOCKADDR_IN serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(port);
    if (bind(serverFd, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
        printf("bind error");
        return -1;
    }

    if (listen(serverFd, SOMAXCONN) < 0) {
        printf("listen error");
        return -1;
    }

    while (true) {
        printf("wait connect...\n");
        int len = sizeof(SOCKADDR);
        SOCKADDR_IN clientAddr;
        int clientFd = accept(serverFd, (SOCKADDR*)&clientAddr, &len);
        if (clientFd == SOCKET_ERROR) {
            printf("accept error");
            break;
        }
        printf("new connect = %d\n", clientFd);
        doClient(clientFd);
    }

    closesocket(serverFd);
    return 0;
}

void doClient(int clientFd) {
    char bufRecv[2000] = {0};
    int bufRecvSize = recv(clientFd, bufRecv, 2000, 0);
    printf("bufRecvSize=%d, bufRecv=%s\n", bufRecvSize, bufRecv);

    char uri[100] = {0};
    const char* sep = "\n";
    char* line = strtok(bufRecv, sep);
    while (line) {
        if (strstr(line, "GET")) {
            if (sscanf(line, "GET %s HTTP/1.1\r\n", &uri) != 1) {
                printf("parse uri error\n");
                return ;
            }
        }
        line = strtok(NULL, sep);
    }
    printf("uri=%s\n", uri);


    std::string filename = "D:\\code\\CLion\\study\\media_stream\\data\\hls" + std::string(uri);
    printf("file:%s\n", filename.data());
    FILE* fp = fopen(filename.data(), "rb");
    if (!fp) {
        printf("fopen %s error\n", filename.data());
        return;
    }

    int bufLen = fread(buf, 1, sizeof(buf), fp);
    printf("bufLen=%d\n", bufLen);

    if (fp) {
        fclose(fp);
    }

    char http_headers[2000];
    if (0 == strcmp("/index.m3u8", uri)) {
        sprintf(http_headers, "HTTP/1.1 200 OK\r\n"
            "Access-Control-Allow-Origin: * \r\n"
            "Connection: keep-alive\r\n"
            "Content-Length: %d\r\n"
            "Content-Type: application/vnd.apple.mpegurl; charset=utf-8\r\n"
            "Keep-Alive: timeout=30, max=100\r\n"
            "Server: hlsServer\r\n"
            "\r\n",
            bufLen);
    } else {
        sprintf(http_headers, "HTTP/1.1 200 OK\r\n"
          "Access-Control-Allow-Origin: * \r\n"
          "Connection: close\r\n"
          "Content-Length: %d\r\n"
          "Content-Type: video/mp2t; charset=utf-8\r\n"
          "Keep-Alive: timeout=30, max=100\r\n"
          "Server: hlsServer\r\n"
          "\r\n",
          bufLen);
    }

    int http_headers_len = strlen(http_headers);
    printf("http_headers_len=%d\n", http_headers_len);

    send(clientFd, http_headers, http_headers_len, 0);
    send(clientFd, buf, bufLen, 0);

    Sleep(10);
    return;
}